#ifndef _DragonBall_external_board_
#define _DragonBall_external_board_

#include <cstring>
#include <fstream>
#include "external_board.h"
#include "igslib_iutil.h"
#include "igslib_ipacket.h"
#include "igslib_iserial_manager.h"
#include "protocol_MH.h"
#include "igslib_iutil.h"
#include "optiga_host.h"
#include "optiga_curve.h"
#include "optiga.h"
#include "mh_mcu.h"

namespace igslib
{
    class ikv:public IProtectUnit
    {
        protected:
            ISpi* m01spi;
            optiga_session  auth_session;
            State ikv_state;
            int ROMaddr;
        public:
            ikv()
            {
                m01spi = nullptr;
                ikv_state= State::Idle;
                ROMaddr=0;
            }
            void SetSPI(ISpi* value) { m01spi = value; }
            bool SendRequest(const std::string&,const std::vector<char>&) ;
            std::vector<char> Withdraw() ;
            void Deposit(const std::vector<char>&) ;
            bool Authenticate() ;
            IProtectUnit::State GetState() ;
            void ProtectEnable();
			void Frozen();
    };
    bool ikv::SendRequest(const std::string& request,const std::vector<char>& args)
    {
        unsigned char dtmp[100];
        unsigned short rand = 0x0;

        optiga_uid      chip_id;
        unsigned short  pid = 0, vid = 0;
        unsigned char  chip_odc[78] = {0};
        uint8_t         *byteptr;

        GetState();
        GetState();
        if(ikv_state!=State::Idle)
            return false;


        if(request=="deposit")
        {
            ROMaddr=args[0];
            return true;
        }
        else if(request=="withdraw")
        {  
            Protocol_DragonBall* tmp = (Protocol_DragonBall*)(m01spi);			
            tmp->ikv_SetROMaddr(args[0]);
            ikv_state=State::WaitWithdraw;
            return true;
        }
        else if(request=="authenticate")
        {
            Protocol_DragonBall* tmp = (Protocol_DragonBall*)(m01spi);			
            int rlen=tmp->ikv_GetInfo(dtmp);

            pid=(dtmp[1]<<8) |dtmp[0];
            vid=(dtmp[3]<<8) |dtmp[2];

            for(int i=0;i<10;i++)
                chip_id.bn[i] = dtmp[4+i];
            for(int i=0;i<78;i++)
                chip_odc[i] = dtmp[14+i];
			
			rand= util::GetTicks()&0xffff;
            /* 1. rand init */ 
            optiga_hst_rand_init(rand);  //產生32B  rand pool
            /* 2. session init with chip id */
            optiga_hst_session_init(&auth_session, &chip_id);
            /* 3. select public key for odc (certificate) verification with pid, vid (can be hardcoded 0x05, 0x05) */
            optiga_curve_init(pid, vid);


            /* 4. verify odc */
            if (optiga_hst_verify_odc (&auth_session, chip_odc) == FALSE) {
                //printf("[ERROR] ODC Verification Failed. \n");
                return 0;
            } else {
                //printf("[DEBUG] ODC Verification OK. \n");
            }

            /* 5. generate challenge */
            optiga_hst_gen_challenge (&auth_session);
            byteptr = (uint8_t *)auth_session.lambda;
            // printf("lambda    = [");
            // for (int i = 0; i < sizeof(auth_session.lambda); i++) {
            // printf("%02X ", byteptr[i]);
            // }
            // printf("]\n");
            byteptr = (uint8_t *)auth_session.challenge;
            // printf("challenge = [");
            // for (int i = 0; i < sizeof(auth_session.challenge); i++) {
            // printf("%02X ", byteptr[i]);
            // }
            // printf("]\n");  	  
            tmp->ikv_SetChallenge(byteptr);

            ikv_state=State::WaitAuthentication;
            return true;
        }
        return false;

    }
    std::vector<char> ikv::Withdraw()
    {
        std::vector<char> rtn;
        unsigned char cptr[16];

        Protocol_DragonBall* tmp = (Protocol_DragonBall*)(m01spi);
        tmp->ikv_GetROMData(cptr);
        for(int i=0;i<16;i++)
            rtn.push_back((char)cptr[i]);		
        return rtn;
    }
    void ikv::Deposit(const std::vector<char>& data)
    {
        char cptr[16];
        int len=data.size();
        if(len<16)				
        {
            memcpy(cptr,&data,len);	
            for(int i=0;i<(16-len);i++)
                cptr[len+i]=0;
        }
        else
            memcpy(cptr,&data[0],16);

        Protocol_DragonBall* tmp = (Protocol_DragonBall*)(m01spi);
        tmp->ikv_SetROMData(ROMaddr,cptr);
        ikv_state=State::WaitWithdraw;
    }
    bool ikv::Authenticate()
    {  
        bool rtn = false;
        unsigned char dtmp[32],response[22],mac_data[10];
        Protocol_DragonBall* tmp = (Protocol_DragonBall*)(m01spi);
        tmp->ikv_GetResponse(dtmp);

        for(int i=0;i<22;i++)
            response[i]=dtmp[i];
        for(int i=0;i<10;i++)
            mac_data[i]=dtmp[i+22];

        // printf("response:");
        // for(int i=0;i<22;i++)
        // printf("0x%02x,",response[i]);
        // printf("\n");

        /* 6. verify response */
        if (optiga_hst_verify_response (&auth_session, response, mac_data) == FALSE){
            //printf("[ERROR] Response Verification Failed. \n");
            rtn= false;
        } else { 
            //printf("[DEBUG] Response Verification OK. \n");
            rtn= true;
        }		
        return rtn;
    }
    IProtectUnit::State ikv::GetState()
    {
        Protocol_DragonBall* tmp = (Protocol_DragonBall*)(m01spi); 
        if(tmp->ikv_IsBusy()==true)
        {       
            return ikv_state;
        }
        else
        {
            ikv_state=State::Idle;
            return ikv_state;
        }
    }
    void ikv::ProtectEnable()
    {
        Protocol_DragonBall* tmp = (Protocol_DragonBall*)(m01spi);
        tmp->ikv_Setup();
		ikv_state=State::WaitIKVWork;
    }
	
	void ikv::Frozen()
    {
        Protocol_DragonBall* tmp = (Protocol_DragonBall*)(m01spi);
        tmp->ikv_DLock();
    }

};

#endif
